<?php 

/**
 * A geocoder adapter for the Google Maps Geocode API
 * {@link http://code.google.com/apis/maps/documentation/services.html#Geocoding_Direct}
 *
 * @category Noginn
 * @package Noginn_Geocoder
 */

class Noginn_Geocoder_Adapter_GoogleMaps extends Noginn_Geocoder_Adapter_Abstract
{
    /** Base Google Maps Geocode API API URI */
    const SERVICE_URI = 'http://maps.google.com/maps/geo';
    
    /**
     * The API key
     *
     * @var string
     */
    protected $_apiKey;

    /**
     * The HTTP client to use for API requests
     *
     * @var Zend_Http_Client
     */
    protected $_httpClient;
    
    /**
     * Constructor
     *
     * @param array|Zend_Config $config 
     * @return void
     */
    public function __construct($config = array())
    {
        iconv_set_encoding('output_encoding', 'UTF-8');
        iconv_set_encoding('input_encoding', 'UTF-8');
        iconv_set_encoding('internal_encoding', 'UTF-8');

        if (array_key_exists('apiKey', $config)) {
            $this->setApiKey($config['apiKey']);
        }
    }
    
    /**
     * Set the API key
     *
     * @param string $apiKey 
     * @return void
     */
    public function setApiKey($apiKey)
    {
        $this->_apiKey = $apiKey;

        return $this;
    }

    /**
     * Return the API key
     *
     * @return string
     */
    public function getApiKey()
    {
        return $this->_apiKey;
    }
    
    /**
     * Return the HTTP client. 
     * If not set then create a new instance of Zend_Http_Client.
     *
     * @return Zend_Http_Client
     */
    public function getHttpClient()
    {
        if (is_null($this->_httpClient)) {
            $this->_httpClient = new Zend_Http_Client(self::SERVICE_URI);
        }

        return $this->_httpClient;
    }
    
    /**
     * Specify the Zend_Http_Client object to use
     *
     * @param Zend_Http_Client $httpClient 
     * @return void
     */
    public function setHttpClient(Zend_Http_Client $httpClient)
    {
        $httpClient->setUri(self::SERVICE_URI);
        $this->_httpClient = $httpClient;
    }
    
    /**
     * Geocode the address
     *
     * @param string|array $address 
     * @return Noginn_Geocoder_LocationList
     */
    public function geocode($address)
    {
        $address = $this->_buildQuery($address);
        
        // Check if the current location list was cached yet
        $cache = Noginn_Geocoder::getCache();
        
        if ($cache !== null) {
            $cacheId = 'Noginn_Geocoder_GoogleMaps_' . md5($address);

            if (false !== ($locationList = $cache->load($cacheId))) {
                return $locationList;
            }
        }
        
        $httpClient = $this->getHttpClient();
        $httpClient->resetParameters();
        $httpClient->setParameterGet('output', 'xml');
        $httpClient->setParameterGet('key', $this->getApiKey());
        $httpClient->setParameterGet('q', $address);
        $response = $httpClient->request('GET');
        
        // Check for HTTP client request errors
        if ($response->isError()) {
            throw new Zend_Exception('An error occurred sending the geocode request. Status code: ' . $response->getStatus());
        }
        
        $xml = new SimpleXMLElement($response->getBody());
        
        // Check for API errors
        $status = $xml->Response->Status->code;
        if ($status != 200)
        {
            switch ($status)
            {
                case '400': // G_GEO_BAD_REQUEST
                    throw new Zend_Exception('The request could not be successfully parsed.');
                    break;
                    
                case '500': // G_GEO_SERVER_ERROR
                    throw new Zend_Exception('The request could not be successfully processed, yet the exact reason for the failure is not known.');
                    break;
 
                case '601': // G_GEO_MISSING_QUERY | G_GEO_MISSING_ADDRESS
                    throw new Zend_Exception('An empty address was specified as input.');
                    break;
                    
                case '602': // G_GEO_UNKNOWN_ADDRESS
                    throw new Zend_Exception('No corresponding geographic location could be found for the specified address.');
                    break;
                    
                case '603': // G_GEO_UNAVAILABLE_ADDRESS
                    throw new Zend_Exception('The geocode for the given address query cannot be returned due to legal or contractual reasons.');
                    break;

                case '610': // G_GEO_BAD_KEY
                    throw new Zend_Exception('The given API key is either invalid or does not match the domain for which it was given.');
                    break;
                    
                case '620': // G_GEO_TOO_MANY_QUERIES
                    throw new Zend_Exception('The given API key has gone over the requests limit in the 24 hour period.');
                    break;
            }
        }
        
        // Parse the result set
        $locationList = new Noginn_Geocoder_LocationList();
        foreach ($xml->Response->Placemark as $result)
        {
            $location = new Noginn_Geocoder_Adapter_GoogleMaps_Location();
            
            $location->accuracy = (int) $result->AddressDetails['Accuracy'];
            
            list($longitude, $latitude, $altitude) = explode(',', $result->Point->coordinates);
            $location->latitude = (float) $latitude;
            $location->longitude = (float) $longitude;
            $location->altitude = (float) $altitude;
            
            $location->street = (string) $result->AddressDetails->Country->AdministrativeArea->Locality->Thoroughfare->ThoroughfareName;
            $location->city = (string) $result->AddressDetails->Country->AdministrativeArea->Locality->LocalityName;
            $location->region = (string) $result->AddressDetails->Country->AdministrativeArea->AdministrativeAreaName;
            $location->country = (string) $result->AddressDetails->Country->CountryNameCode;
            $location->postalCode = (string) $result->AddressDetails->Country->AdministrativeArea->Locality->PostalCode->PostalCodeNumber;
            
            $location->address = (string) $result->address;
            
            $locationList->addLocation($location);
        }
        
        // If caching is enabled, cache the current result
        if ($cache !== null) {
            $cache->save($locationList, $cacheId);
        }
        
        return $locationList;
    }
}